/**
 * @fileOverview
 * WashizuMahjong.
 * http://code.google.com/p/washizu-mahjong/
 */
/**
 * @namespace
 */
var Hand = new function(){
	/**
	 * @constructor
	 * @this {TileArr}
	 * @param {MahjongTile[]} tilehand
	 * @property {MahjongTile[][9]}	Pin	筒子
	 * @property {MahjongTile[][9]}	Sou	索子
	 * @property {MahjongTile[][9]}	Wan	萬子
	 * @property {MahjongTile[]}	East	東
	 * @property {MahjongTile[]}	South	南
	 * @property {MahjongTile[]}	West	西
	 * @property {MahjongTile[]}	North	北
	 * @property {MahjongTile[]}	Haku	白
	 * @property {MahjongTile[]}	Hatsu	發
	 * @property {MahjongTile[]}	Chun	中
	 */
	function TileArr(tilehand){
		this.Pin = [[],[],[],[],[],[],[],[],[]];
		this.Sou = [[],[],[],[],[],[],[],[],[]];
		this.Wan = [[],[],[],[],[],[],[],[],[]];
		this.East = [];
		this.West = [];
		this.South = [];
		this.North = [];
		this.Haku = [];
		this.Hatsu = [];
		this.Chun = []
		var t = this;
		if(tilehand && tilehand instanceof Array){
			tilehand.forEach(function(tile, index, arr){
				if(!(tile instanceof MahjongTile))
					return;
				switch(tile.typeCode){
					case 0:	//餅
						t.Pin[tile.simpleNum - 1].push(tile);
						break;
					case 1:	//萬
						t.Wan[tile.simpleNum - 1].push(tile);
						break;
					case 2:	//索
						t.Sou[tile.simpleNum - 1].push(tile);
						break;
					case 3:	//字牌
						switch (tile.wordNum){
							case 0: //東
								t.East.push(tile);
								break;
							case 1:	//南
								t.South.push(tile);
								break;
							case 2:	//西
								t.West.push(tile);
								break;
							case 3:	//北
								t.North.push(tile);
								break;
							case 4:	//白
								t.Haku.push(tile);
								break;
							case 5:	//發
								t.Hatsu.push(tile);
								break;
							case 6:	//中
								t.Chun.push(tile);
								break;
						}
						break;
				}
			});
		}
	};
	TileArr.prototype.Pin = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.Sou = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.Wan = [[],[],[],[],[],[],[],[],[]];
	TileArr.prototype.East = [];
	TileArr.prototype.West = [];
	TileArr.prototype.South = [];
	TileArr.prototype.North = [];
	TileArr.prototype.Haku = [];
	TileArr.prototype.Hatsu = [];
	TileArr.prototype.Chun = [];
	/**
	 * clone the current TileArr
	 * @return {TileArr}
	 */
	TileArr.prototype.clone = function(){
		var t = new TileArr;
		t.Pin = this.Pin.clone();
		t.Sou = this.Sou.clone();
		t.Wan = this.Wan.clone();
		t.East = this.East.clone();
		t.West = this.West.clone();
		t.South = this.South.clone();
		t.North = this.North.clone();
		t.Haku = this.Haku.clone();
		t.Hatsu = this.Hatsu.clone();
		t.Chun = this.Chun.clone();
	}
	/**
	 * The recursive function of determining the tile array. This function only support for the tiles with word.
	 * @see Hand-countNumMeld
	 */
	var countWordMeld = function(/** MahjongTile[]  */ tilearr, /** [HandInfo[]] */ resultarr, /** [HandInfo] */ handinfo){
		if(!tilearr || !(tilearr instanceof Array) )
			return false;
		if(!resultarr || !(resultarr instanceof Array))
			resultarr = [];
		if(!handinfo || !(handinfo instanceof HandInfo)){
			handinfo = new HandInfo
		}
		var _test_tiles, h, haspair = false;
		switch(tilearr.length){
			default:
			case 3:
				//視為刻子
				h = handinfo.clone()
				_test_tiles = tilearr.splice(0,3)
				h.concealedTriplets.push(_test_tiles);
				resultarr.push(h);
				tilearr.splice(0,0, _test_tiles[0], _test_tiles[1], _test_tiles[2]);
			case 2:
				// 視為對子
				haspair = true
				h = handinfo.clone()
				_test_tiles = tilearr.splice(0,2);
				h.pairs.push(_test_tiles);
				resultarr.push(h);
				//tilearr.splice(0,0, _test_tiles[0], _test_tiles[1]);

			case 1:
				// unused
				if(!haspair){
					h = handinfo.clone()
				}
				while(tilearr.length){
					h.unused.push(tilearr.splice(0,1)[0])
				}
				if(!haspair)
					resultarr.push(h);
			case 0:
				break;
		}
		return resultarr
	}
	/**
	 * The recursive function of determining the tile array. This function only support for the tiles with number.
	 * @return {HandInfo[] | Boolean}
	 * @see Hand-countWordMeld
	 */
	var countNumMeld = function(/** MahjongTile[][9]  */ tilearr, /** [HandInfo[]] */ resultarr, /** [HandInfo] */ handinfo){
		var _is_top = false, is_bottom = true;

		if(!tilearr || !(tilearr instanceof Array) )
			return false;
		if(!resultarr || !(resultarr instanceof Array))
			resultarr = [];
		if(!handinfo || !(handinfo instanceof HandInfo)){
			_is_top = true;
			is_bottom = false;
			handinfo = new HandInfo
		}
		for(var i = 0; i < tilearr.length; i++){
			var tiles = tilearr[i];

			// 視為刻子
			if(tiles.length >= 3){
				var _test_tiles = tiles.splice(0,3),	//進行判斷的tiles
				    h = handinfo.clone();
				h.concealedTriplets.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				//put tiles back
				tilearr[i].splice(0,0, _test_tiles[0], _test_tiles[1], _test_tiles[2]);
			}

			// 視為順子
			if( i+2 < tilearr.length &&
				tilearr[i].length >= 1 && tilearr[i+1].length >= 1 && tilearr[i+2].length >= 1 ){
				var _test_tiles = [
					tilearr[i].splice(0,1)[0],
					tilearr[i+1].splice(0,1)[0],
					tilearr[i+2].splice(0,1)[0]
				],
				    h = handinfo.clone();

				h.straight.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				tilearr[i].push(_test_tiles[0])
				tilearr[i+1].push(_test_tiles[1])
				tilearr[i+2].push(_test_tiles[2])
			}

			// 視為對子
			if(tiles.length >= 2){
				var _test_tiles = tiles.splice(0,2),
				    h = handinfo.clone();
				h.pairs.push(_test_tiles);
				var r = countNumMeld(tilearr.slice(0,tilearr.length), resultarr, h);
				if(r == false){
					is_bottom = false;
				}
				tiles.splice(0,0, _test_tiles[0], _test_tiles[1])
			}

			//順子／對子／刻子的判斷完畢，假設為無需使用的牌
			if(tiles.length>0){
				tiles.forEach(function(val, index, arr){
					handinfo.unused.push( val )
				})
				tilearr[i] = []
			}
		}
		if(_is_top || is_bottom){
			var _arr = []
			for(var i = handinfo.unused.length - 1; i > 0; i--){
				var _t1 = handinfo.unused[i],
				    _t2 = handinfo.unused[i-1];
				switch(Math.abs(_t1.simpleNum - _t2.simpleNum)){
					case 1:
					case 2:
						_arr.push( handinfo.unused.splice(i-1, 2) )
						i--;
						break;
					default:
						break;
				}
			}
			for(i  = 0 ; i < _arr.length; i++){
				handinfo.unused.push(_arr[i])
			}

			resultarr.push(handinfo)

			if(_is_top)
				return resultarr
			else if(is_bottom)
				return true
		}else{
			return false
		}
	}

/**
 * Sort the MahjongTile array &ldquo;h&rdquo;
 * @param {MahjongTile[]}h
 * @see Player#hand
 * @return {MahjongTile[]} The sorted result.
 */
	this.sort = function(h){
		if(!(typeof(h)=='object'&&(h instanceof Array) )) return;
		while(h.indexOf(undefined)>= 0)
			h.remove(undefined);
		while(h.indexOf(null)>= 0)
			h.remove(null);
		var sortMahjong = function (a,b){
			if(a.typeCode != b.typeCode){
				return a.typeCode - b.typeCode;
			}else if(a.simpleNum != b.simpleNum){
				return a.simpleNum - b.simpleNum;
			}else{
				return a.wordNum - b.wordNum;
			}
		}
		h.sort(sortMahjong);
		return h;
	}

/**
 * To count the numbers of concealedTriplets (暗刻), pairs(對子), and straight(順子) in the hand
 * @param {MahjongTile[]} hand
 * @see Player#hand
 * @return {HandInfo}
 */
	this.countMeldsInHand = function(hand){
		if(!(hand instanceof Array))
			return;
		var _h = hand,
		    index,
		    hand_style = new TileArr(_h);

		var p = countNumMeld(hand_style.Pin),
		    s = countNumMeld(hand_style.Sou),
		    w = countNumMeld(hand_style.Wan),
		    east = countWordMeld(hand_style.East),
		    west = countWordMeld(hand_style.West),
		    south = countWordMeld(hand_style.South),
		    north = countWordMeld(hand_style.North),
		    haku = countWordMeld(hand_style.Haku),
		    hatsu = countWordMeld(hand_style.Hatsu),
		    chun = countWordMeld(hand_style.Chun);

		var can_done_array = [], list = [p,s,w,east,west,south,north,haku,hatsu,chun];
		list.forEach(function(val, index, array){
			if(val.length > 0){
				can_done_array.push(val)
			}
		})
		var resultArray = [];

		var setup_result = function(handinfo, resultarr, resultarrindex){
			if(resultarrindex >= resultarr.length){
				return
			}
			for(var i = 0; i < resultarr[resultarrindex].length; i++){
				var h = handinfo.join(resultarr[resultarrindex][i]);
				if(resultarrindex + 2 <= resultarr.length){
					setup_result(h, resultarr, resultarrindex + 1);
				}else if(resultarrindex = resultarr.length - 1){
					resultArray.push(h)
				}else{
					continue;
				}
			}
		}
		setup_result(new HandInfo ,can_done_array, 0)

		var result = []
		resultArray.forEach(function(val, index, array){
			//Debug, sometimes numbers of tiles in HandInfo != hand.length
			if((!val instanceof HandInfo))
				return
			var tile_length = val.concealedTriplets.length * 3 +
					val.pairs.length * 2 +
					val.straight.length * 3;
			for(var i = 0; i < val.unused.length; i++){
				if(val.unused[i] instanceof Array){
					tile_length += val.unused[i].length
				}else if(val.unused[i] instanceof MahjongTile){
					tile_length += 1
				}
			}
			if(tile_length == hand.length){
				result.push(val)
			}
		})
		return result;
	};

/**
 * To check whether the HandInfo is ready
 * @param {HandInfo} info
 * @see Hand.countMeldsInHand
 * @see Hand.isFinishHand
 * @return {Boolean}
 */
	this.isReadyHand = function(info){
		if(!(info instanceof HandInfo))
			return false
		var tile_length = info.concealedTriplets.length * 3 +
				info.pairs.length * 2 +
				info.straight.length * 3;
		for(var i = 0; i < info.unused.length; i++){
			if(info.unused[i] instanceof Array){
				tile_length += info.unused[i].length
			}else if(info.unused[i] instanceof MahjongTile){
				tile_length += 1
			}
		}

		if(tile_length % 3 == 2)
			return this.isFinishHand(info);
		else if(tile_length % 3 != 1)
			return false;
		if(info.unused.length == 1 && info.pairs.length <= 1){
			//info.pairs.length == 1 => 為順子 / 刻子缺一張
			//info.pairs.length == 0 => 對子缺一張，單騎
			return true
		}
		if(info.unused.length == 0 && info.pairs.length == 2){
			// e.g. 🀋🀋🀏🀏🀙🀚🀛
			return true
		}
		if(tile_length == 13 && info.pairs.length == 6){
			// 7 pairs
			return true
		}
		if(tile_length == 13 &&
			((info.pairs.length == 1 && info.unused.length == 11) ||
			 (info.pairs.length == 0 && info.unused.length == 13)) ){
			//国士無双
			var a = info.unused.clone()
			for(var i = 0; i < info.pairs.length; i++){
				for(var j = 0; j < info.pairs[i].length; j++){
					a.push(info.pairs[i][j])
				}
			}
			var all_9 = true
			a.forEach(function(val, index, arr){
				if(all_9 == false)
					return
				if(!(val.typeCode == 3 || (val.simpleNum == 1 || val.simpleNum == 9)))
					all_9 = false

			});
			if(all_9 == true){
				return true
			}
		}
		return false;
	}

/**
 * To check whether the HandInfo is a finish hand
 * @param {HandInfo} info
 * @see Hand.countMeldsInHand
 * @see Hand.isReadyHand
 * @return {Boolean}
 */
	this.isFinishHand = function(info){
		if(!(info instanceof HandInfo))
			return false
		var tile_length = info.concealedTriplets.length * 3 +
				info.pairs.length * 2 +
				info.straight.length * 3;
		// do not count info.unused.length, because if the hand is finish, it won't have any unused tiles.

		if(tile_length % 3 != 2)
			return false;
		if(info.unused.length != 0 && info.unused.length != 12){
			//if it is 国士無双, info.unused.length will be 12
			return false
		}
		if(info.pairs.length != 1 && info.pairs.length != 7){
			//if the hand is finished, the pair must in the hand not in the meld
			//if it is チートイツ, info.pairs.length will be 7
			return false
		}
		return true
	}
}
/**
 * This class is the detail of the list of the Mahjong tile hand.
 * @constructor
 * @see Hand.countMeldsInHand
 */
function HandInfo(){
	if(!(this instanceof HandInfo))
		return new HandInfo
	this.concealedTriplets = [];
	this.pairs= [];
	this.straight= [];
	this.unused = [];
}
/**
 * 暗刻
 * @type MahjongTile[][]
 */
HandInfo.prototype.concealedTriplets = [];
/**
 * 對子
 * @type MahjongTile[][]
 */
HandInfo.prototype.pairs= [];
/**
 * 順子
 * @type MahjongTile[][]
 */
HandInfo.prototype.straight= [];
/**
 * The tiles which is not used
 * @type MahjongTile[]
 */
HandInfo.prototype.unused = [];
/**
 * Clone the information detail
 * @return {HandInfo}
 */
HandInfo.prototype.clone = function(){
	var clonedObj = new HandInfo;
	clonedObj.concealedTriplets = this.concealedTriplets.slice(0, this.concealedTriplets.length);
	clonedObj.pairs = this.pairs.slice(0, this.pairs.length);
	clonedObj.straight = this.straight.slice(0, this.straight.length)
	clonedObj.unused = this.unused.slice(0, this.unused.length);
	return clonedObj;
}
/**
 * Mix another HandInfo class
 * @param {HandInfo} h
 * @return {HandInfo}
 */
HandInfo.prototype.join = function(h){
	if(!(h instanceof HandInfo))
		return this;
	var _this = this.clone(), _h = h.clone(), i;
	for(i = 0; i < _h.concealedTriplets.length; i++)
		_this.concealedTriplets.push(_h.concealedTriplets[i]);
	for(i = 0; i < _h.pairs.length; i++)
		_this.pairs.push(_h.pairs[i]);
	for(i = 0; i < _h.straight.length; i++)
		_this.straight.push(_h.straight[i]);
	for(i = 0; i < _h.unused.length; i++)
		_this.unused.push(_h.unused[i]);

	return _this;
}
